﻿namespace DistrRTgen
{
    using DistrRTgenWebservice;
    using rtgen_worker;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Management;
    using System.Net;
    using System.Security.Cryptography;
    using System.Text;
    using System.Threading;
    using System.IO.Compression;
    using ComponentAce.Compression.Libs.zlib;

    internal class DistrRTgenConnector
    {
        private static MachineInfo _MachineInfo;
        private static DistrRTGenService _SOAP = new DistrRTGenService();

        public DistrRTgenConnector()
        {
            if (_SOAP == null)
            {
                _SOAP = new DistrRTGenService();                
            }
            if ((_MachineInfo == null) || (_MachineInfo.Credentials.Password != Settings.Password))
            {
                _MachineInfo = new MachineInfo();
                _MachineInfo.Credentials = new Credentials();
                _MachineInfo.Credentials.Username = Settings.Username;
                _MachineInfo.Credentials.Password = Settings.Password;
                _MachineInfo.ClientID = Settings.ClientID;
                _MachineInfo.Version = "4.0 BETA 3";
                _MachineInfo.Hostname = Environment.MachineName;
                _MachineInfo.SupportHybrid = 1;
                ObjectQuery objectQuery = new ObjectQuery("select * from Win32_Processor");
                ManagementObjectCollection vals = new ManagementObjectSearcher(objectQuery).Get();
                int i = 0;
                _MachineInfo.CPU = new CPU[vals.Count];
                try
                {
                    foreach (ManagementObject val in vals)
                    {
                        CPU cpu = new CPU();
                        cpu.CurrentSpeed = Convert.ToInt32(val.GetPropertyValue("CurrentClockSpeed"));
                        cpu.MaxSpeed = Convert.ToInt32(val.GetPropertyValue("MaxClockSpeed"));
                        switch (Convert.ToInt32(val.GetPropertyValue("AddressWidth")))
                        {
                            case 0x20:
                                cpu.Architecture = "x86";
                                break;

                            case 0x40:
                                cpu.Architecture = "x64";
                                break;

                            default:
                                cpu.Architecture = "unknown";
                                break;
                        }
                        try
                        {
                            cpu.NumCores = Convert.ToInt32(val.GetPropertyValue("NumberOfCores"));
                        }
                        catch (Exception)
                        {
                            cpu.NumCores = 1;
                        }
                        _MachineInfo.CPU[i++] = cpu;
                    }
                }
                catch (Exception ex)
                {
                    Logger.AddLogLine("Unable to retrieve WMI info: " + ex.Message, LoggerLevel.LOG_ERROR);
                }
            }
            ServicePointManager.Expect100Continue = false;
            if ((_MachineInfo.ClientID == 0) && (_MachineInfo.Credentials.Username != ""))
            {
                this.RegisterClient();
            }
        }

        private bool CheckForNetworkConnection()
        {
            try
            {
                Dns.GetHostEntry("distributed.freerainbowtables.com");
                return true;
            }
            catch
            {
                return false;
            }
        }

        private void CopyStream(Stream input, Stream output)
        {
            int len;
            byte[] buffer = new byte[0x7d0];
            while ((len = input.Read(buffer, 0, 0x7d0)) > 0)
            {
                output.Write(buffer, 0, len);
            }
            output.Flush();
        }

        public int RegisterClient()
        {
            if (_MachineInfo.ClientID > 0)
            {
                return 0;
            }
            try
            {
                int ErrorCode;
                int ClientID = _SOAP.RegisterNewClient(_MachineInfo, out ErrorCode);
                if (ErrorCode == 0)
                {
                    _MachineInfo.ClientID = ClientID;
                    Settings.ClientID = ClientID;
                    return ErrorCode;
                }
                Logger.AddLogLine("Could not register new client. Error code " + ErrorCode, LoggerLevel.LOG_ERROR);
                return ErrorCode;
            }
            catch (WebException ex)
            {
                Logger.AddLogLine("Unable to contact remote server for client registration: " + ex.Message, LoggerLevel.LOG_DEBUG);
            }           
            catch (Exception ex)
            {
                Logger.AddLogLine("Could not register new client. Error returned: " + ex.Message, LoggerLevel.LOG_ALERT);
            }
            return -1;
        }

        public bool ReportError(string ErrorString)
        {
            if (!this.CheckForNetworkConnection())
            {
                return false;
            }
            try
            {
                Logger.AddLogLine("Reporting error '" + ErrorString + "' to freerainbowtables.com", LoggerLevel.LOG_DEBUG);
                _SOAP.ReportError(_MachineInfo, ErrorString);
            }
            catch (WebException ex) { }
            catch (Exception ex)
            {
                Logger.AddLogLine("Exception occured while reporting error: " + ex.Message, LoggerLevel.LOG_INFO);
            }
            return true;
        }

        public DistrRTgen.ChainCheckHash[] RequestChainCheck(uint NumHash)
        {
            if (_MachineInfo.ClientID == 0)
            {
                Logger.AddLogLine("Can't request for new work. Client is not registered yet", LoggerLevel.LOG_DEBUG);
                return null;
            }
            if (!Settings.AcceptNewParts)
            {
                Logger.AddLogLine("Can't request for new ChainChain work. AcceptNewParts is disabled", LoggerLevel.LOG_DEBUG);
                return null;
            }
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Can't request for ChainCheck work. Network is down", LoggerLevel.LOG_INFO);
                return null;
            }
            try
            {
                int ErrorCode;
                Logger.AddLogLine("Requesting for " + NumHash + " ChainCheck work", LoggerLevel.LOG_DEBUG);
                DistrRTgenWebservice.ChainCheckHash[] HashList = _SOAP.RequestChainCheck(_MachineInfo, NumHash, Convert.ToUInt32(Settings.EnablePublicHashCracking), out ErrorCode);
                if (ErrorCode != 0)
                {
                    switch (ErrorCode)
                    {
                        case 1:
                            throw new InvalidUsernameOrPasswordException("Invalid credentials: " + _MachineInfo.Credentials.Username + " // " + _MachineInfo.Credentials.Password);
                    }
                    Logger.AddLogLine("Recieved unknown error code " + ErrorCode + " from server during RequestChainCheck()", LoggerLevel.LOG_INFO);
                }
                if (HashList.Length > 0)
                {
                    Logger.AddLogLine(HashList.Length + " pieces of ChainCheck work recieved", LoggerLevel.LOG_DEBUG);
                    DistrRTgen.ChainCheckHash[] ChainCheckHashList = new DistrRTgen.ChainCheckHash[HashList.Length];
                    for (int i = 0; i < HashList.Length; i++)
                    {
                        string[] chains = HashList[i].Chains.Split(new char[] { '-' });
                        Queue<RainbowCrackChain> qChains = new Queue<RainbowCrackChain>();
                        Logger.AddLogLine("ChainCheck work contains " + chains.Length + " chains to check", LoggerLevel.LOG_DEBUG);
                        for (int j = 0; j < (chains.Length - 1); j++)
                        {
                            try
                            {
                                if (chains[j] == "")
                                {
                                    Logger.AddLogLine("Invalid ChainCheck recieved from server. Ignoring", LoggerLevel.LOG_DEBUG);
                                    qChains = null;
                                    break;
                                }
                                RainbowCrackChain pChain = new RainbowCrackChain();
                                string[] parts = chains[j].Split(new char[] { ';' });
                                pChain.nStartingPoint = Convert.ToUInt64(parts[0]);
                                pChain.nGuessedPos = Convert.ToUInt32(parts[1]);
                                qChains.Enqueue(pChain);
                            }
                            catch (Exception ex)
                            {
                                Logger.AddLogLine("Exception happened in DistrRTgenConnector::RequestChainCheck(): " + ex.Message, LoggerLevel.LOG_DEBUG);
                                qChains = null;
                                break;
                            }
                        }
                        if (qChains != null)
                        {
                            ChainCheckHashList[i] = new DistrRTgen.ChainCheckHash(HashList[i].RequestID, HashList[i].Hash, HashList[i].HashRoutine, HashList[i].Charset, HashList[i].ChainLength, HashList[i].MinLetters, HashList[i].MaxLetters, HashList[i].Index, HashList[i].Salt, Convert.ToDateTime(HashList[i].ExpireTime), qChains);
                        }
                    }
                    return ChainCheckHashList;
                }
                Logger.AddLogLine("No ChainCheck work recieved", LoggerLevel.LOG_DEBUG);
            }
            catch (InvalidUsernameOrPasswordException)
            {
                Logger.AddLogLine("Failed to request for new work: Invalid username or password", LoggerLevel.LOG_ALERT);
            }
            catch (WebException ex)
            {
                Logger.AddLogLine("Unable to contact remote server: " + ex.Message, LoggerLevel.LOG_DEBUG);
            }           
            catch (Exception ex)
            {
                Logger.AddLogLine(string.Concat(new object[] { "Exception of type ", ex.GetType(), " occured while requesting for ChainCheck work: ", ex.Message }), LoggerLevel.LOG_ALERT);
            }
            return null;
        }

        public DistrRTgen.IndexHash[] RequestHashToIndex(uint NumHash)
        {
            if (_MachineInfo.ClientID == 0)
            {
                Logger.AddLogLine("Can't request for new work. Client is not registered yet", LoggerLevel.LOG_DEBUG);
                return null;
            }
            if (!Settings.AcceptNewParts)
            {
                Logger.AddLogLine("Can't request for new work. AcceptNewParts is disabled", LoggerLevel.LOG_DEBUG);
                return null;
            }
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Can't request for work. Network is down", LoggerLevel.LOG_INFO);
                return null;
            }
            try
            {
                int ErrorCode;
                Logger.AddLogLine("Requesting for " + NumHash + " HashToIndex work", LoggerLevel.LOG_DEBUG);
                DistrRTgenWebservice.IndexHash[] HashList = _SOAP.RequestHashToIndex(_MachineInfo, NumHash, Convert.ToUInt32(Settings.EnablePublicHashCracking), out ErrorCode);
                if (ErrorCode != 0)
                {
                    switch (ErrorCode)
                    {
                        case 1:
                            throw new InvalidUsernameOrPasswordException("");
                    }
                }
                if (HashList.Length > 0)
                {
                    Logger.AddLogLine(HashList.Length + " pieces of HashToIndex work recieved", LoggerLevel.LOG_DEBUG);
                    DistrRTgen.IndexHash[] IndexHashList = new DistrRTgen.IndexHash[HashList.Length];
                    for (int i = 0; i < HashList.Length; i++)
                    {
                        IndexHashList[i] = new DistrRTgen.IndexHash(HashList[i].RequestID, HashList[i].Hash, HashList[i].HashRoutine, HashList[i].Charset, HashList[i].ChainLength, HashList[i].MinLetters, HashList[i].MaxLetters, HashList[i].Index, HashList[i].Salt, Convert.ToDateTime(HashList[i].ExpireTime));
                    }
                    return IndexHashList;
                }
                Logger.AddLogLine("No HashToIndex work recieved", LoggerLevel.LOG_DEBUG);
            }
            catch (InvalidUsernameOrPasswordException)
            {
                Logger.AddLogLine("Failed to request for new work: Invalid username or password", LoggerLevel.LOG_ALERT);
            }
            catch (WebException ex)
            {
                Logger.AddLogLine("Unable to contact remote server: " + ex.Message, LoggerLevel.LOG_DEBUG);
            }           
            catch (Exception ex)
            {
                Logger.AddLogLine("Exception occured while requesting for HashToIndex work: " + ex.Message, LoggerLevel.LOG_INFO);
            }
            return null;
        }

        public DistrRTgen.WorkUnit[] RequestWork(int NumWU)
        {
            if (_MachineInfo.ClientID == 0)
            {
                Logger.AddLogLine("Can't request for new work. Client is not registered yet", LoggerLevel.LOG_DEBUG);
                return null;
            }
            if (!Settings.AcceptNewParts)
            {
                Logger.AddLogLine("Can't request for new work. AcceptNewParts is disabled", LoggerLevel.LOG_DEBUG);
                return null;
            }
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Can't request for work. Network is down", LoggerLevel.LOG_INFO);
                return null;
            }
            Logger.AddLogLine("Requesting " + NumWU + " pieces of work from server", LoggerLevel.LOG_DEBUG);
            try
            {
                int ErrorCode;
                string CharsetMD5;
                rtgenDetails rtgenbinary;
                DistrRTgenWebservice.WorkUnit[] WU = _SOAP.RequestWork(_MachineInfo, NumWU, out CharsetMD5, out rtgenbinary, out ErrorCode);
                switch (ErrorCode)
                {
                    case 1:
                        throw new InvalidUsernameOrPasswordException("");
                }
                if ((rtgenbinary.Architecture != Settings.cmdArchitecture) || (rtgenbinary.Version != Settings.cmdVersion))
                {
                    if (!this.UpdateBinary(rtgenbinary.Architecture, rtgenbinary.Version))
                    {
                        return null;
                    }
                    Settings.cmdArchitecture = rtgenbinary.Architecture;
                    Settings.cmdVersion = rtgenbinary.Version;
                }
                DistrRTgen.WorkUnit[] WUArray = new DistrRTgen.WorkUnit[WU.Length];
                Logger.AddLogLine("Recieved " + WU.Length + " pieces of work", LoggerLevel.LOG_DEBUG);
                if (System.IO.File.Exists("charset.txt"))
                {
                    byte[] charset = System.IO.File.ReadAllBytes("charset.txt");
                    byte[] hash = new MD5CryptoServiceProvider().ComputeHash(charset);
                    string sHash = "";
                    foreach (byte b in hash)
                    {
                        sHash = sHash + b.ToString("x2");
                    }
                    if (sHash != CharsetMD5)
                    {
                        this.UpdateCharset("charset.txt");
                    }
                }
                else
                {
                    this.UpdateCharset("charset.txt");
                }
                for (int i = 0; i < WU.Length; i++)
                {
                    if (WU[i].PartID == 0)
                    {
                        return WUArray;
                    }
                    WUArray[i] = new DistrRTgen.WorkUnit(WU[i].PartID, WU[i].HashRoutine, WU[i].Charset, WU[i].MinLetters, WU[i].MaxLetters, WU[i].Index, WU[i].ChainLength, WU[i].ChainCount, WU[i].ChainStart, WU[i].Salt);
                }
                return WUArray;
            }
            catch (InvalidUsernameOrPasswordException)
            {
                Logger.AddLogLine("Failed to request for new work: Invalid username or password", LoggerLevel.LOG_ALERT);
            }
            catch (WebException ex)
            {
                Logger.AddLogLine("Unable to contact remote server: " + ex.Message, LoggerLevel.LOG_DEBUG);
            }           
            catch (Exception ex)
            {
                Logger.AddLogLine(string.Concat(new object[] { "Exception of type ", ex.GetType(), " happened in DistrRTgenConnector::RequestWork(): ", ex.Message }), LoggerLevel.LOG_DEBUG);
            }
            return null;
        }

        public bool SendChainCheckResult(uint RequestID, string Password)
        {
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Can't send ChainCheck result. Network is down", LoggerLevel.LOG_INFO);
                return false;
            }
            try
            {
                Logger.AddLogLine("Sending ChainCheck result for Request " + RequestID, LoggerLevel.LOG_DEBUG);
                _SOAP.SendChainCheckResult(_MachineInfo, RequestID, Password);
            }
            catch (WebException ex)
            {
                Logger.AddLogLine("Unable to contact remote server: " + ex.Message, LoggerLevel.LOG_DEBUG);
            }
            catch (Exception ex)
            {
                Logger.AddLogLine(string.Concat(new object[] { "Exception occured while sending ChainCheck result for Request ", RequestID, ": ", ex.Message }), LoggerLevel.LOG_INFO);
            }
            return true;
        }

        private bool UpdateBinary(string Architecture, string Version)
        {
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Unable to download distrrtgen binary. Network is down", LoggerLevel.LOG_INFO);
                return false;
            }
            try
            {
                WebClient client = new WebClient();
                string Filename = "distrrtgen_" + Architecture + "_" + Version + ".exe";
                if (!Directory.Exists("bin"))
                {
                    Directory.CreateDirectory("bin");
                }
                client.DownloadFile(Settings.BaseURL + "bin/" + Filename, "bin/" + Filename);
                return true;
            }
            catch (WebException ex)
            {
                Logger.AddLogLine("Unable to update distrrtgen: " + ex.Message, LoggerLevel.LOG_ALERT);
                return false;
            }
        }

        public bool UpdateCharset(string sCharsetFile)
        {
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Unable to update charset.txt. Network is down", LoggerLevel.LOG_INFO);
                return false;
            }
            HttpWebRequest Request = (HttpWebRequest) WebRequest.Create(Settings.BaseURL + "charset.txt");
            Request.KeepAlive = false;
            Request.Timeout = -1;
            Request.Method = "GET";
            try
            {
                Stream httpResponseStream = Request.GetResponse().GetResponseStream();
                byte[] bbResponse = new byte[0x4c4b40];
                int nTotalRead = 0;
                try
                {
                    while (true)
                    {
                        int nRead = httpResponseStream.Read(bbResponse, nTotalRead, 0x7a120);
                        if (nRead <= 0)
                        {
                            goto Label_0098;
                        }
                        nTotalRead += nRead;
                        Thread.Sleep(0x3e8);
                    }
                }
                catch (IOException)
                {
                    return false;
                }
            Label_0098:
                httpResponseStream.Close();
                string sResult = Encoding.ASCII.GetString(bbResponse, 0, nTotalRead);
                System.IO.File.WriteAllText(sCharsetFile, sResult);
                return true;
            }
            catch (WebException ex)
            {
                Logger.AddLogLine("Unable to update charset.txt: " + ex.Message, LoggerLevel.LOG_INFO);
                return false;
            }
        }

        public bool UploadFinishedPart(WorkUnit WU, UploadFileCompletedEventHandler UploadFileCompleted, UploadProgressChangedEventHandler UploadProgressChanged)
        {
            if ((Settings.UploadStartTime.TimeOfDay > DateTime.Now.TimeOfDay) || (Settings.UploadEndTime.TimeOfDay < DateTime.Now.TimeOfDay))
            {
                Logger.AddLogLine("Upload of part " + WU.PartID + " not initiated. Its out of the time schedule", LoggerLevel.LOG_DEBUG);
                return false;
            }
            if (!Settings.UploadParts)
            {
                Logger.AddLogLine("Upload of part " + WU.PartID + " not initiated. Part uploading is disabled", LoggerLevel.LOG_DEBUG);
                return false;
            }
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Unable to upload finished part " + WU.PartID + ". Network connection is down. Will try later", LoggerLevel.LOG_INFO);
                return false;
            }
            WU.BeginUploading();
            string sFilename = WU.PartID + ".part";
            string sURL = Settings.BaseURL + "upload.php?username=" + Settings.Username + "&password=" + Settings.Password + "&compressed=true";
            CustomWebClient Request = new CustomWebClient();
            ServicePointManager.MaxServicePointIdleTime = 5;
            Request.UploadFileCompleted += UploadFileCompleted;
            Request.UploadProgressChanged += UploadProgressChanged;
            try
            {

                FileStream outFileStream = new FileStream(sFilename + ".zip", FileMode.Create);
                ZOutputStream outZStream = new ZOutputStream(outFileStream, -1);

                FileStream inFileStream = File.OpenRead(sFilename);
                try
                {
                    this.CopyStream(inFileStream, outZStream);
                }
                finally
                {
                    outZStream.Close();
                    outFileStream.Close();
                    inFileStream.Close();
                }                
            }
            catch (IOException ex)
            {

                Logger.AddLogLine("Recieved IOException while compressing part file: " + ex.Message + " Source: " + ex.Source, LoggerLevel.LOG_INFO);
                WU.EndUploading();

                return false;
            }
            Request.UploadFileAsync(new Uri(sURL), "POST", sFilename + ".zip", WU.PartID);
            return true;
        }

        public bool UploadHashIndexList(DistrRTgen.IndexHash Hash)
        {
            if (!this.CheckForNetworkConnection())
            {
                Logger.AddLogLine("Can't upload HashIndex List. Network is down", LoggerLevel.LOG_INFO);
                return false;
            }
            try
            {
                int ErrorCode = _SOAP.UploadHashIndexList(_MachineInfo, Hash.RequestID, Hash.IndexList);
                if (ErrorCode != 0)
                {
                    switch (ErrorCode)
                    {
                        case 200:
                            Logger.AddLogLine("The generated IndexList was not accepted by the server due to invalid data. This means your machine is generating invalid data", LoggerLevel.LOG_ALERT);
                            break;

                        case 0xc9:
                            Logger.AddLogLine("The IndexList is no longer assigned to me. Dropping it", LoggerLevel.LOG_DEBUG);
                            break;

                        case 1:
                            throw new InvalidUsernameOrPasswordException("Invalid credentials: " + _MachineInfo.Credentials.Username + " // " + _MachineInfo.Credentials.Password);
                    }
                }
                return true;
            }
            catch (InvalidUsernameOrPasswordException)
            {
                Logger.AddLogLine("Failed to upload hash index list: Invalid username or password", LoggerLevel.LOG_ALERT);
            }
            catch (WebException)
            {
                Logger.AddLogLine("Unable to contact remote server", LoggerLevel.LOG_DEBUG);
            }
            catch (Exception ex)
            {
                Logger.AddLogLine(string.Concat(new object[] { "Exception of type ", ex.GetType(), " occured while uploading HashIndex list: ", ex.Message, " Stack: ", ex.StackTrace }), LoggerLevel.LOG_INFO);
            }
            return false;
        }
    }
}

